﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Microsoft.Devices;
using System.Threading;
using Microsoft.Devices.Sensors;
using System.Windows.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Phone.Controls.Maps;
using Microsoft.Phone.Controls.Maps.Platform;
using System.Device.Location;
using Microsoft.Phone.Shell;
using System.Windows.Controls.Primitives;
using System.ComponentModel;
using System.Xml.Linq;

namespace AugmentedReality
{
    public partial class webcam : PhoneApplicationPage
    {
        //Variables Camera
        PhotoCamera cam = new PhotoCamera();
        private static ManualResetEvent pauseFramesEvent = new ManualResetEvent(true);
        //Variables Accelerometer
        Accelerometer accelerometer;
        DispatcherTimer timer;
        Vector3 acceleration;
        bool isAccelerometerDataValid;
        double angle;
        //Variables GPS
        GeoCoordinateWatcher _geoCoordinateWatcher;
        GeoCoordinate location; //Declared @ SetLocation --- 13 decimal position in lat & lon
        //Variables Compass
        Compass compass;
        double trueHeading;
        double headingAccuracy;
        bool isCompassDataValid;
        bool calibrating = false;

        //Loading popup
        private Popup popup;
        private BackgroundWorker backroungWorker;

        XDocument loadedData;
        Feed f1;
        TextBox[] placeBox = new TextBox[100];

        bool isDownloaded=false;

        public webcam()
        {
            InitializeComponent();
            ShowPopup();
            StartAccelerometer();
            StartCompass();
        }

        #region "Loading"
        private void ShowPopup()
        {
            this.popup = new Popup();
            this.popup.Child = new PopupSplash();
            this.popup.IsOpen = true;
            this.popup.Width = 800;
            StartLoadingData();
        }

        private void StartLoadingData()
        {
            backroungWorker = new BackgroundWorker();
            backroungWorker.DoWork += new DoWorkEventHandler(backroungWorker_DoWork);
            backroungWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backroungWorker_RunWorkerCompleted);
            backroungWorker.RunWorkerAsync();
        }

        void backroungWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.Dispatcher.BeginInvoke(() =>
            {
                this.popup.IsOpen = false;

            }
            );
        }

        void backroungWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            StartGPS();
        }
        #endregion

        //Return an angle for 0 to 360 clockwise, starting from North, as variable trueHeading does relating to Compass angle from North
        double calcPhiFromMyPos(GeoCoordinate myPos, double latitude, double longitude)
        {
            double deltaX = longitude - myPos.Longitude;
            double deltaY = latitude - myPos.Latitude;
            double phiRad = Math.Atan2(deltaY , deltaX);
            double phiDeg = MathHelper.ToDegrees((float)phiRad);
            double phiNormalized = ( (- phiDeg) + 360.0 ) % 360.0;
            //if (isDownloaded)
                //MessageBox.Show(longitude + " " + myPos.Longitude +" " + deltaX + " " + deltaY + " " + phiRad + " " + phiDeg + " " + phiNormalized);
            return phiNormalized;
        }

        double phi2PosPercent(double phi)
        {
            double coeffPercent;
            if (phi <= 45.0)
            {
                coeffPercent = ((0.5 * phi) / 45.0) + 0.5;
            }
            else if (phi >= 315.0)
            {
                coeffPercent = ((0.5 * (phi - 360.0)) / 45.0) + 0.5;
            }
            else
            {
                coeffPercent = -1.0;
            }
            return coeffPercent;
        }

        #region "Accelerometer"

        //Start accelerometer and update value every 50ms
        private void StartAccelerometer()
        {
            if (!Accelerometer.IsSupported)
            {
                //Device does not support compass
            }
            else
            {
                // Initialize the timer and add Tick event handler, but don't start it yet.
                timer = new DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(50);
                timer.Tick += new EventHandler(timer_Tick);
            }
            accelerometer = new Accelerometer();

            //update timer
            accelerometer.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
            accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);

            try
            {
                //starting accelerometer;
                accelerometer.Start();
                timer.Start();
            }
            catch (InvalidOperationException)
            {
                //unable to start accelerometer
            }
        }

        // On accelerometer Value Changed update acceleration
        void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
        {
            //For the ACCELEROMETER
            isAccelerometerDataValid = accelerometer.IsDataValid;
            acceleration = e.SensorReading.Acceleration;
            //End Accelerometer

            //For the COMPASS - Determine compass axis --> All this code is used for determine whether the compass is using negative axis
            /*Vector3 v = e.SensorReading.Acceleration;
            bool isCompassUsingNegativeZAxis = false;
            if (Math.Abs(v.Z) < Math.Cos(Math.PI / 4) && (v.Y < Math.Sin(7 * Math.PI / 4)))
            {
                isCompassUsingNegativeZAxis = true;
            }
            //Dispatcher.BeginInvoke(() => { orientationTextBlock.Text = (isCompassUsingNegativeZAxis) ? "portrait mode" : "flat mode"; });*/
            //End Compass
        }

        //Execute code at every tick
        void timer_Tick(object sender, EventArgs e)
        {
            if (isAccelerometerDataValid)
            {
                //take angle of inclination
                 angle = Math.Abs(Math.Atan2(acceleration.X, acceleration.Z));
                //Handle the transition between Webcam and Map. transition angle: 2.6 rad
                 if (angle > 2.6) //Webcam-->MapView
                 {
                     map.Visibility = Visibility.Visible;
                     mapLocator.Visibility = Visibility.Visible;
                     /*if (!_geoCoordinateWatcher.TryStart(true, TimeSpan.FromSeconds(5)))
                     {
                         MessageBox.Show("Please enable Location Service on the Phone.", "Warning", MessageBoxButton.OK);
                     }*/
                     cameraContent.Visibility = Visibility.Collapsed;
                 }
                 else //MapView-->Webcam
                 {
                     cameraContent.Visibility = Visibility.Visible;
                     //_geoCoordinateWatcher.Stop();
                     mapLocator.Visibility = Visibility.Collapsed;
                     map.Visibility = Visibility.Collapsed;
                 }
            }

            //If the device request Compass Calibration or not
            if (!calibrating)
            {
                if (isCompassDataValid)
                {
                    lblStatus.Text = "receiving data from compass.";
                }
                try
                {
                    movePlace();
                }
                catch { }
            }
            else
            {
                if (headingAccuracy <= 10)
                {
                    calibrationTextBlock.Foreground = new SolidColorBrush(Colors.Green);
                    calibrationTextBlock.Text = "Complete!";
                }
                else
                {
                    calibrationTextBlock.Foreground = new SolidColorBrush(Colors.Red);
                    calibrationTextBlock.Text = headingAccuracy.ToString("0.0");
                }
            }
        }

        #endregion

        #region "Navigation and Orientation Change"

        //Routine when you Come on the Page. Start the stream and show it
        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            if ((PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true) ||
                 (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing) == true))
            {
                // Initialize the default camera.
                cam = new Microsoft.Devices.PhotoCamera();
                //Event is fired when the PhotoCamera object has been initialized
                cam.Initialized += new EventHandler<Microsoft.Devices.CameraOperationCompletedEventArgs>(cam_Initialized);
                //Set the VideoBrush source to the camera
                viewfinderBrush.SetSource(cam);
            }
            else
            {
                // The camera is not supported on the device.
                this.Dispatcher.BeginInvoke(delegate()
                {
                    // Write message.
                    MessageBox.Show("A Camera is not available on this device.");
                });
            }
        }

        //Stop the stream when you Navigate out the page
        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            if (cam != null)
            {
                // Dispose of the camera to minimize power consumption and to expedite shutdown.
                cam.Dispose();
                // Release memory, ensure garbage collection.
                cam.Initialized -= cam_Initialized;
            }
        }

        //On Orientation Changed also change the camera orientation
        protected override void OnOrientationChanged(OrientationChangedEventArgs e)
        {
            if (cam != null)
            {
                int landscapeRightRotation = 180;
                if (cam.CameraType == CameraType.FrontFacing) landscapeRightRotation = -180;
                // Rotate video brush from camera.
                if (e.Orientation == PageOrientation.LandscapeRight)
                {
                    // Rotate for LandscapeRight orientation.
                    viewfinderBrush.RelativeTransform =
                        new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = landscapeRightRotation };
                }
                else
                {
                    // Rotate for standard landscape orientation.
                    viewfinderBrush.RelativeTransform =
                        new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 0 };
                }
            }
            base.OnOrientationChanged(e);
        }


        private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            if ((e.Orientation & PageOrientation.Portrait) == (PageOrientation.Portrait))
            {
                LayoutPortrait.Visibility = Visibility.Visible;
                LayoutLandscape.Visibility = Visibility.Collapsed;
            }
            else
            {
                LayoutPortrait.Visibility = Visibility.Collapsed;
                LayoutLandscape.Visibility = Visibility.Visible;
            }
        }

        #endregion

        #region "Camera"

        //Update Interface if initialization succeeds
        void cam_Initialized(object sender, Microsoft.Devices.CameraOperationCompletedEventArgs e)
        {
            if (e.Succeeded)
            {
                this.Dispatcher.BeginInvoke(delegate()
                {
                    //txtDebug.Text = "Camera initialized";
                });
            }
        }

        #endregion

        #region "gps map"
        //Code that has to be executed in Main
        private void StartGPS()
        {
            // Delcare GeoCoordinateWatcher with high accuracy in order to use the device's GPS
            _geoCoordinateWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
            _geoCoordinateWatcher.MovementThreshold = 100;
            // Subscribe to the device's status changed event
            _geoCoordinateWatcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(_geoCoordinateWatcher_StatusChanged);
            // Subscribe to the device's position changed event to receive GPS coordinates (longitude and latitude)
            _geoCoordinateWatcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(_geoCoordinateWatcher_PositionChanged);
            if (!_geoCoordinateWatcher.TryStart(true, TimeSpan.FromSeconds(5)))
            {
                MessageBox.Show("Please enable Location Service on the Phone.", "Warning", MessageBoxButton.OK);
            }
        }

        private void _geoCoordinateWatcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() => StatusChanged(e));
        }
        //IN REFRESH: Handle the status of the GPS device
        private void StatusChanged(GeoPositionStatusChangedEventArgs e)
        {
            switch (e.Status)
            {
                case GeoPositionStatus.Disabled:
                    lblStatus.Text = "Location Service is disabled!";
                    break;
                case GeoPositionStatus.Initializing:
                    lblStatus.Text = "Initializing Location Service...";
                    break;
                case GeoPositionStatus.NoData:
                    lblStatus.Text = "Your position could not be located.";
                    break;
                case GeoPositionStatus.Ready:
                    lblStatus.Text = "";
                    break;
            }
        }

        private void _geoCoordinateWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() => ChangePosition(e));
        }

        private void ChangePosition(GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            SetLocation(e.Position.Location.Latitude, e.Position.Location.Longitude, 15, true);
        }

        //IN REFRESH: Set my location (location), centering the map at it, set the pushpin (mapLocator) at my location, start to blink it
        private void SetLocation(double latitude, double longitude, double zoomLevel, bool showLocator)
        {
            location = new GeoCoordinate(latitude, longitude);
            map.SetView(location, zoomLevel);
            mapLocator.Location = location;
            MapLayer.SetPosition(locator, location);
            if (showLocator)
            {
                locator.Visibility = Visibility.Visible;
                BlinkLocator.Begin();
            }
            else
            {
                locator.Visibility = Visibility.Collapsed;
                BlinkLocator.Stop();
            }
            takePlace();
        }

        #endregion

        #region "Compass"
        private void StartCompass()
        {
            if (!Compass.IsSupported)
            {
                // The device on which the application is running does not support the compass sensor.
                // Alert the user and hide the application bar.
                lblStatus.Text = "device does not support compass";
                //ApplicationBar.IsVisible = false;
            }
            else
            {
                // Initialize the timer and add Tick event handler, but don't start it yet.
                /*timer = new DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(30);
                timer.Tick += new EventHandler(timer_Tick);*/

                // Instantiate the compass.
                compass = new Compass();

                // Specify the desired time between updates. The sensor accepts intervals in multiples of 20 ms.
                compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);

                // The sensor may not support the requested time between updates.
                // The TimeBetweenUpdates property reflects the actual rate.
                //timeBetweenUpdatesTextBlock.Text = compass.TimeBetweenUpdates.TotalMilliseconds + " ms";

                compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged);
                compass.Calibrate += new EventHandler<CalibrationEventArgs>(compass_Calibrate);

                try
                {
                    lblStatus.Text = "starting compass.";
                    compass.Start();
                    timer.Start();

                    // Start accelerometer for detecting compass axis
                    /*accelerometer = new Accelerometer();
                    accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
                    accelerometer.Start();*/
                }
                catch (InvalidOperationException)
                {
                    lblStatus.Text = "unable to start compass.";
                }
            }
        }

        void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
        {
            // Note that this event handler is called from a background thread
            // and therefore does not have access to the UI thread. To update 
            // the UI from this handler, use Dispatcher.BeginInvoke() as shown.
            // Dispatcher.BeginInvoke(() => { statusTextBlock.Text = "in CurrentValueChanged"; });

            isCompassDataValid = compass.IsDataValid;

            trueHeading = e.SensorReading.TrueHeading;
            headingAccuracy = Math.Abs(e.SensorReading.HeadingAccuracy);
        }

        void compass_Calibrate(object sender, CalibrationEventArgs e)
        {
            Dispatcher.BeginInvoke(() => { LayoutLandscape.Visibility = Visibility.Collapsed; Calibrate.Visibility = Visibility.Visible; calibrationStackPanel.Visibility = Visibility.Visible; });
            calibrating = true;
        }

        private void calibrationButton_Click(object sender, RoutedEventArgs e)
        {
            calibrationStackPanel.Visibility = Visibility.Collapsed;
            calibrating = false;
            Calibrate.Visibility = Visibility.Collapsed;
            LayoutLandscape.Visibility = Visibility.Visible;
        }
        #endregion

        //TODO: Implementing Compass stop. DO NOT CANCEL
        /*if (compass != null && compass.IsDataValid)
        {
            // Stop data acquisition from the compass.
            compass.Stop();
            timer.Stop();
            lblStatus.Text = "compass stopped.";

            // Detect compass axis
            accelerometer.Stop();
        }*/

        // Download feed from Google Place
        private void takePlace()
        {
            f1 = new Feed("ts", "https://maps.googleapis.com/maps/api/place/search/xml?location=" + location.Latitude.ToString().Replace(',', '.') + "," + location.Longitude.ToString().Replace(',', '.') + "&radius=5000&sensor=false&key=AIzaSyCJTUvQC0QyQ3VQyQSlL728i2C4Moiga7U");
            f1.StartDownload();
        }

        //Load Feed
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            loadedData = f1.LoadFeed();
            var data = from c in loadedData.Descendants("PlaceSearchResponse").Descendants("result")
                       select new Place
                       {
                           Name = (string)c.Element("name"),
                           Longitude = (String)c.Element("geometry").Element("location").Element("lng"),
                           Latitude = (String)c.Element("geometry").Element("location").Element("lat"),
                           //Description = (string)c.Element("description"),
                           Photo = (string)c.Element("icon"),
                       };
            placeList.ItemsSource = data;
            //TODO: Fix Procedure
            // Create new Textbox list
            for (int i = 0; i < placeList.Items.Count(); ++i)
            {
                placeBox[i] = new TextBox();
                placeBox[i].Width = 200;
                placeBox[i].Height = 100;
                placeBox[i].Background = new SolidColorBrush(Colors.Red);
                ((Canvas)LayoutLandscape).Children.Add(placeBox[i]);
            }
            isDownloaded = true;
        }

        private void movePlace()
        {
            loadedData = f1.LoadFeed();
            var data = from c in loadedData.Descendants("PlaceSearchResponse").Descendants("result")
                       select new Place
                       {
                           Name = (string)c.Element("name"),
                           Longitude = (String)c.Element("geometry").Element("location").Element("lng"),
                           Latitude = (String)c.Element("geometry").Element("location").Element("lat"),
                           //Description = (string)c.Element("description"),
                           Photo = (string)c.Element("icon"),
                       };
            int i = 0;
            foreach (Place m in data)
                {
                string latitude2=m.Latitude.ToString().Replace(',', '.');
                double latitude3;
                Double.TryParse(latitude2,out latitude3);

                string longitude2=m.Longitude.ToString().Replace(',', '.');
                double longitude3;
                Double.TryParse(longitude2, out longitude3);
                //TODO: phase tra 230 e 250!!!
                double phase = ((calcPhiFromMyPos(location, latitude3 / 10000000, longitude3 / 10000000) - trueHeading) + 360.0) % 360.0; //Relative phase displacement
                double positionPixelOnScreen = (phi2PosPercent(phase) * 800);

                placeBox[i].Text = m.Name.ToString();
                placeBox[i].SetValue(Canvas.LeftProperty, positionPixelOnScreen);
                ++i;
            }

        }
        //TODO: fix position for giroscope calibration

    }      

}